#pragma once

#include "CoreMinimal.h"
#include "LRG_Node_GridCell.h"
#include "ReplicationGraph.h"
#include "Define/SpatialCoordinate.h"
#include "LRG_Node_GridSpatial2D.generated.h"

class ULineReplicationGraphComponent;

UCLASS()
class LINEREPLICATIONGRAPH_API ULRG_Node_GridSpatial : public UReplicationGraphNode
{
	GENERATED_BODY()

public:
	ULRG_Node_GridSpatial();

	virtual void Serialize(FArchive& Ar) override;
	virtual void NotifyAddNetworkActor(const FNewReplicatedActorInfo& Actor) override;	
	virtual bool NotifyRemoveNetworkActor(const FNewReplicatedActorInfo& ActorInfo, bool bWarnIfNotFound=true) override;
	virtual void NotifyResetAllNetworkActors() override;
	virtual void PrepareForReplication() override;
	virtual void GatherActorListsForConnection(const FConnectionGatherActorListParameters& Params) override;

	void AddActor_Static(ULineReplicationGraphComponent* Component, FGlobalActorReplicationInfo& ActorRepInfo);
	void AddActor_Static(const FNewReplicatedActorInfo& ActorInfo, FGlobalActorReplicationInfo& ActorRepInfo) { AddActorInternal_Static(ActorInfo, ActorRepInfo, false); }
	void AddActor_Dynamic(ULineReplicationGraphComponent* Component);
	void AddActor_Dormancy(ULineReplicationGraphComponent* Component);
	void AddActor_Dormancy(const FNewReplicatedActorInfo& ActorInfo, FGlobalActorReplicationInfo& ActorRepInfo);

	void RemoveActor_Static(const FNewReplicatedActorInfo& ActorInfo);
	void RemoveActor_Dynamic(ULineReplicationGraphComponent* Component) { RemoveActorInternal_Dynamic(Component); }
	void RemoveActor_Dormancy(const FNewReplicatedActorInfo& ActorInfo);
	
	float		CellSize;
	double		ConnectionMaxZ; // Connection locations have to be <= to this to pull from the grid

	// Allow graph to override function for creating cell nodes in this grid.
	TFunction<ULRG_Node_GridCell*(ULRG_Node_GridSpatial* Parent)>	CreateCellNodeOverride;

	// When enabled the RepGraph tells clients to destroy dormant dynamic actors when they go out of relevancy.
	bool bDestroyDormantDynamicActors = true;

protected:
	void AddActor_Static_Internal(ULineReplicationGraphComponent* Component, FGlobalActorReplicationInfo& ActorRepInfo, bool IsDormancyDriven);
	/** For adding new actor to the graph */
	virtual void AddActorInternal_Dynamic(const FNewReplicatedActorInfo& ActorInfo);
	virtual void AddActorInternal_Static(const FNewReplicatedActorInfo& ActorInfo, FGlobalActorReplicationInfo& ActorRepInfo, bool IsDormancyDriven); // Can differ the actual call to implementation if actor is not ready
	virtual void AddActorInternal_Static_Implementation(const FNewReplicatedActorInfo& ActorInfo, FGlobalActorReplicationInfo& ActorRepInfo, bool IsDormancyDriven);

	virtual void RemoveActorInternal_Dynamic(ULineReplicationGraphComponent* Component);
	virtual void RemoveActorInternal_Static(const FNewReplicatedActorInfo& Actor, FGlobalActorReplicationInfo& ActorRepInfo, bool WasAddedAsDormantActor);

protected:
	// Classmap of actor classes which CANNOT force a rebuild of the spatialization tree. They will be clamped instead. E.g, projectiles.
	TClassMap<bool> ClassRebuildDenyList;
	
	struct FActorCellInfo
	{
		bool IsValid() const { return StartX != -1; }
		void Reset() { StartX = -1; }
		int32 StartX=-1;
		int32 StartY;
		int32 EndX;
		int32 EndY;
	};
	
	UPROPERTY()
	TSet<ULineReplicationGraphComponent*> DynamicActorComponentSet;

	struct FCachedStaticActorInfo
	{
		FCachedStaticActorInfo(const FNewReplicatedActorInfo& InInfo, bool bInDormDriven) : ActorInfo(InInfo), bDormancyDriven(bInDormDriven) { }
		FNewReplicatedActorInfo ActorInfo;
		bool bDormancyDriven = false; // This actor will be removed from static actor list if it becomes non dormant.
	};
	TMap<FActorRepListType, FCachedStaticActorInfo> StaticSpatializedActors;

	// Static spatialized actors that were not fully initialized when registering with this node. We differ their placement in the grid until the next frame
	struct FPendingStaticActors
	{
		FPendingStaticActors(const FActorRepListType& InActor, const bool InDormancyDriven) : Actor(InActor), DormancyDriven(InDormancyDriven) { }
		bool operator==(const FActorRepListType& InActor) const { return InActor == Actor; };
		FActorRepListType Actor;
		bool DormancyDriven;
	};
	TArray<FPendingStaticActors> PendingStaticSpatializedActors;

	void OnNetDormancyChange(FActorRepListType Actor, FGlobalActorReplicationInfo& GlobalInfo, ENetDormancy NewVlue, ENetDormancy OldValue);
	
	void PutStaticActorIntoCell(const FNewReplicatedActorInfo& ActorInfo, FGlobalActorReplicationInfo& ActorRepInfo, bool bDormancyDriven);
	
	ULRG_Node_GridCell* GetCell(int32 X, int32 Y);
	ULRG_Node_GridCell* TryCreateCell(int32 X, int32 Y);
	void CalculateActorLocation(const FVector& Location);
	void UpdateLocationCell(ULineReplicationGraphComponent* Component);

	void GetGridNodesForActor(FActorRepListType Actor, const FGlobalActorReplicationInfo& ActorRepInfo, TArray<ULRG_Node_GridCell*>& OutNodes);
	void GetGridNodesForActor(FActorRepListType Actor, const FActorCellInfo& CellInfo, TArray<ULRG_Node_GridCell*>& OutNodes);
	TMap<int32 , ULRG_Node_GridCell*> GetGridNodesForActorByCellInfo(const ULRG_Node_GridSpatial::FActorCellInfo& CellInfo);
	
	virtual FActorCellInfo GetCellInfoForActor(FActorRepListType Actor, const FVector& Location3D, float CullDistance);

	void DebugCellChange(FActorRepListType& DynamicActor,
		const TMap<int64, ULRG_Node_GridCell*>& PreviousNodesMap, const TMap<int64, ULRG_Node_GridCell*>& NewNodesMap);
protected:
	FSpatialCoordinate LocationToCoordinate(const FVector& Location) const;
	int32 CalculateCoordinate(float LocationValue) const;
protected:
	int64 LocationToId(const FVector& Location3D) const;
	static int32 CalcGridCellKey(int32 X, int32 Y) {
		return FSpatialCoordinate::CoordinateToId(X,Y);
	}
	static void CalcGridCellIndex(int32 Key, int& X, int& Y) {
		FSpatialCoordinate::IdToCoordinate(Key, X, Y);
	}
protected:
	UPROPERTY()
	TMap<int32, ULRG_Node_GridCell*> GridCellMap;
	int PrepareForReplicationCount = 0;
};